package cn.news.upload.service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSONObject;

import cn.news.upload.utils.FileMD5;
import cn.news.upload.utils.HttpClientUtil;
import cn.news.upload.utils.MD5Util;

@Service
public class VideoUploadService {
	private static final Logger LOGGER = LoggerFactory.getLogger(VideoUploadService.class);

	// 视频上传验证md5接口地址
	@Value("${upload.video.url.md5}")
	private String JUDGE_VIDEO_MD5_URL;

	// 视频上传接口地址
	@Value("${upload.video.url}")
	private String UPLOAD_VIDEO_URL;

	/**
	 * 
	 * @param videoFile
	 *            MultipartFile类型的文件对象
	 * @param createUser
	 *            用户
	 * @param appId
	 *            应用id
	 * @param resourceType
	 *            资源类型
	 * @return jsonObject形如： { code: 200, 200:视频库响应正常; 500:视频库响应失败; 0:上传异常
	 *         content: { // 初次上传完成，会包含 "pieceNum":5, "videoId":9215 //
	 *         已经上传过并转码完成，会包含各种路径信息，及"videoId":9217, "exist":1, "pieceSum":5,
	 *         "videoTime":278, "resourceType":"S", "status":1 //
	 *         已经上传过但仍然在转码，则只有 videoId, exist, pieceSum, videoTime,
	 *         resourceType, status字段，无路径信息 } }
	 * @throws IOException
	 */
	public JSONObject uploadMobileVideo(MultipartFile videoFile, String createUser, String appId, String resourceType)
			throws IOException {

		LOGGER.info("upload video----start. videoFile=" + videoFile);

		JSONObject resultObj = new JSONObject();

		// 文件名
		String fileName = videoFile.getOriginalFilename();
		// 文件尺寸
		long fileSize = videoFile.getSize();

		// 文件MD5
		String md5Code = "";
		String sign = "";
		try {
			md5Code = FileMD5.filePartMd5(videoFile.getInputStream());
			DateFormat dtfmt = new SimpleDateFormat("yyyyMMdd");
			sign = MD5Util.string2MD5(appId + "-videoStore-" + dtfmt.format(new Date())); // app签名
		} catch (Exception e) {
			LOGGER.error("视频文件md5计算失败", e);
		}

		// 校验文件参数
		Map<String, String> parameterMap = new HashMap<>();
		parameterMap.put("fileName", URLEncoder.encode(fileName, "UTF-8"));
		parameterMap.put("sign", sign);
		parameterMap.put("appId", appId);
		parameterMap.put("resourceType", resourceType);
		parameterMap.put("createUser", createUser);
		parameterMap.put("md5Code", md5Code);
		parameterMap.put("fileSize", String.valueOf(fileSize));

		LOGGER.info("upload video----parameters: " + parameterMap.toString());

		JSONObject judgeObj = null;

		// 校验视频md5, 请求异常后重试
		String url = JUDGE_VIDEO_MD5_URL;
		int retryCount = 0;
		while (++retryCount <= 3) {
			try {
				String res = HttpClientUtil.sendPost(url, parameterMap, "UTF-8");
				judgeObj = JSONObject.parseObject(res);
			} catch (Exception e) {
				LOGGER.error("第" + retryCount + "次上传失败，重试:", e);
			}

			if (null != judgeObj) {
				LOGGER.info("upload mobile video----judge success:  " + judgeObj.toString());
				break;
			}

			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				LOGGER.error("thread sleep InterruptedException", e);
			}
		}

		Integer code = 0;
		if (null == judgeObj) {
			LOGGER.warn("上传失败：" + url + ", 参数：" + parameterMap.toString());
			resultObj.put("code", code);
			resultObj.put("content", "上传失败");
			return resultObj;
		}

		code = judgeObj.getInteger("code");
		if (null != code && code == 200) {
			JSONObject contentObj = judgeObj.getJSONObject("content");
			int pieceSum = contentObj.getInteger("pieceSum");
			int exist = contentObj.getInteger("exist");

			if (exist == 1) {
				// exist为1 表示上传过
				int status = contentObj.getInteger("status");
				if (status == 1) {
					// 已经上传完了
					resultObj.put("code", code);
					resultObj.put("content", contentObj);
				} else {
					// 未传完
					int pieceNum = contentObj.getInteger("pieceNum");
					String ret = uploadPieces(videoFile.getInputStream(), pieceNum, pieceSum, md5Code, fileName,
							resourceType, appId, createUser);
					resultObj = JSONObject.parseObject(ret);
				}
			} else {
				// exist为0 表示没有传过
				String ret = uploadPieces(videoFile.getInputStream(), 0, pieceSum, md5Code, fileName, resourceType,
						appId, createUser);
				resultObj = JSONObject.parseObject(ret);
			}
		} else {
			resultObj = judgeObj;
		}

		resultObj.put("fileName", fileName);
		return resultObj;
	}

	private String uploadPieces(InputStream is, int pieceNum, int pieceSum, String md5code, String fileName,
			String resourceType, String appId, String operatorId) {

		String ret = "";
		String uploadUrl = UPLOAD_VIDEO_URL;

		// 每一片10MB
		int pieceSize = 1024 * 1024 * 10;
		byte[] pieceBuf = new byte[pieceSize];

		// FIXME 使用RadomAccessFile 上传有问题, 未查明原因,
		// 使用InputStrem则没有采用视频库的断点续传机制，每次都是重新上传
		// RandomAccessFile raFile = new RandomAccessFile(file, "r");

		// InputStream is = null;
		ByteArrayOutputStream bos = null;
		try {
			// is = new FileInputStream(file);

			// while (pieceNum < pieceSum) {
			int count = 0;
			long len = 0;
			while ((len = is.read(pieceBuf)) != -1) {

				// long startPos = pieceNum * pieceSize;
				// raFile.seek(startPos);
				// len = raFile.read(pieceBuf);
				bos = new ByteArrayOutputStream();
				bos.write(pieceBuf);
				HttpEntity httpEntity = new ByteArrayEntity(bos.toByteArray());

				// 提交请求
				StringBuffer sb = new StringBuffer();
				sb.append(uploadUrl);
				sb.append("?pieceNum=").append(String.valueOf(count));
				sb.append("&md5Code=").append(md5code);// 文件md5Code
				sb.append("&fileName=").append(URLEncoder.encode(fileName, "UTF-8"));// fileName
																						// 文件名
				sb.append("&pieceSum=").append(pieceSum);// 视频总块数
				sb.append("&resourceType=").append(resourceType);// S存储的数据源位置,这里存在S库
				sb.append("&appId=").append(appId);// 直播ID
				sb.append("&createUser=").append(operatorId);// 上传用户

				try {
					ret = HttpClientUtil.sendPostWithEntity(sb.toString(), httpEntity, null);
				} catch (Exception e) {
					LOGGER.error("上传视频文件流失败: ", e);
				}

				bos.close();
				count++;
			}

			// raFile.close();
			// is.close();

		} catch (IOException e) {

			LOGGER.error("uploadPieces 失败: ", e);

		} finally {

			try {
				if (null != bos) {
					bos.close();
				}
				if (null != is) {
					is.close();
				}
			} catch (IOException e) {
				LOGGER.error("close stream fail: ", e);
			}
		}

		if ("".equals(ret)) {
			ret = (new JSONObject()).toJSONString();
		}
		return ret;
	}
	
	public JSONObject judgeVideoMd5(MultipartFile videoFile, String createUser, String appId, String resourceType){
		JSONObject judgeObj = null;
		
		try {
			// 文件名
			String fileName = videoFile.getOriginalFilename();
			// 文件尺寸
			long fileSize = videoFile.getSize();

			// 文件MD5
			String md5Code = "";
			String sign = "";
			try {
				md5Code = FileMD5.filePartMd5(videoFile.getInputStream());
				DateFormat dtfmt = new SimpleDateFormat("yyyyMMdd");
				sign = MD5Util.string2MD5(appId + "-videoStore-" + dtfmt.format(new Date())); // app签名
			} catch (Exception e) {
				LOGGER.error("视频文件md5计算失败", e);
			}

			// 校验文件参数
			Map<String, String> parameterMap = new HashMap<>();
			parameterMap.put("fileName", URLEncoder.encode(fileName, "UTF-8"));
			parameterMap.put("sign", sign);
			parameterMap.put("appId", appId);
			parameterMap.put("resourceType", resourceType);
			parameterMap.put("createUser", createUser);
			parameterMap.put("md5Code", md5Code);
			parameterMap.put("fileSize", String.valueOf(fileSize));

			// 校验视频md5, 请求异常后重试
			String url = JUDGE_VIDEO_MD5_URL;
			int retryCount = 0;
			while (++retryCount <= 3) {
				try {
					String res = HttpClientUtil.sendPost(url, parameterMap, "UTF-8");
					judgeObj = JSONObject.parseObject(res);
				} catch (Exception e) {
					LOGGER.error("第" + retryCount + "次上传失败，重试:", e);
				}

				if (null != judgeObj) {
					LOGGER.info("upload mobile video----judge sucess:  " + judgeObj.toString());
					break;
				}

				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					LOGGER.error("thread sleep InterruptedException", e);
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return judgeObj;
	}
}
